//
//  1、audio_play.m
//  OneToOne
//
//  Created by ByteDance on 2023/7/1.
//

#import "1、audio_play.h"
#import <AudioToolbox/AudioToolbox.h>
void playCallback(void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer);
void playCallback(void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
}

@implementation __audio_play{
    AVAudioSession *audioSession;
}

- (instancetype)init {
    
    //1、Audiossion获取硬件单例
    audioSession = [AVAudioSession sharedInstance];
    [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
    //buffer越小延迟越低
    [audioSession setPreferredIOBufferDuration:0.002 error:nil];
    //设置采样频率，让设备按照设置来采集或播放音频
    [audioSession setPreferredSampleRate:44100.0 error:nil];
    //激活
    [audioSession setActive:YES error:nil];
    
}
/*2、
 category是向系统描述应用需要的能力，常用分类：
 AVAudioSessionCategoryPlayback：用于播放录制音乐或者其它声音的类别
 AVAudioSessionCategoryOptionMixWithOthers：混音
 AVAudioSessionCategoryPlayAndRecord : 同时需要录音（输入）和播放（输出）音频的类别，例如 K 歌、RTC 场景。
 */

/*3、
 CategoryOptions 是向系统设置类别的可选项。
 AVAudioSessionCategoryOptionDefaultToSpeaker：此选项只能在使用PlayAndRecord类别时设置。它用于保证在没有使用其他配件（如耳机）的情况音频始终会路由至扬声器而不是听筒。
 AVAudioSessionCategoryOptionAllowBluetooth：此选项代表音频录入和输出全部走蓝牙设备，仅可以为 PlayAndRecord 还有 Record 这两个类别设置这个选项。
 AVAudioSessionCategoryOptionAllowBluetoothA2DP：此选项代表音频可以输出到高音质（立体声、仅支持音频输出不支持音频录入）的蓝牙设备中。如果使用 Playback 类别，系统将自动使用这个 A2DP 选项，如果使用 PlayAndRecord 类别，需要开发者自己手动设置这个选项。
 */

//4、监听音频焦点抢占,一般是音频被打断时处理业务，比如暂停音频时修改icon。
- (void)listen {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioSessionInterruptionNoti:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
}

- (void)audioSessionInterruptionNoti:(NSNotification *)noti {
    AVAudioSessionInterruptionType type = [noti.userInfo[AVAudioSessionInterruptionTypeKey] intValue];
    if (type == AVAudioSessionInterruptionTypeBegan) {
        //do thing
    }
}

//5、监听声音硬件变化，比如插拔耳机时进行提示toset
- (void)listen1 {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRoutrChange:) name:AVAudioSessionRouteChangeNotification object:nil];
}
- (void)audioRoutrChange:(NSNotification*)sender {
    NSInteger reason = [[sender.userInfo valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
    if (reason == AVAudioSessionRouteChangeReasonNewDeviceAvailable || reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
        //do
    } else if (reason == AVAudioSessionRouteChangeReasonCategoryChange) {
        //do
    }
}

//6、申请录音权限（1、）判断有没有询问过（2、）没有就询问（3、）如果拒绝就引导进入设置页面手动打开
- (void)exc {
    AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
    if (status == AVAuthorizationStatusNotDetermined) {//第一次进入渴望获取权限
        [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
                    
        }];
    } else if (status == AVAuthorizationStatusRestricted || status == AVAuthorizationStatusDenied) {
        //还没有授权
        //引导进入设置页面
    } else {
        //已经授权
    }
}

//7、audioToolbox中的audioQueueRef类，audioQueue内部的职责（1）连接音频硬件进行录音和播放（2）管理内存（3）根据配置，使用编解码器进行音频格式转换。
//audioQueue暴露的接口（1）使用正确的音频格式、回调方法等参数创建出AudioQueueRef对象（2）为audioQueueRed分配buffer，将buffer入队，启动audioQueue（3）在audioQueueRef的回调中填充指定格式的音频数据，并且重新入队。（4）暂停、恢复等常规接口。

//8、Audio Queue运行程序分为启动和运行阶段。（1）启动阶段主要是配置和启动audioqueue（2）运行阶段主要是audio queue开始播放之后回调给应用程序填充buffer，并且重新入队，3个buffer周而复始地运行起来；直到应用程序调用AudioQueue的pause或者Stop等接口
//启动
- (void)audioQueueStart {
    AudioStreamBasicDescription dataFormat;
    AudioQueueRef queue;
    AudioQueueNewInput(&dataFormat, playCallback, (__bridge void *)self, NULL, NULL, 0, &queue);
    AudioQueueBufferRef buffer[3];
    for (int i = 0; i < 3; i++) {//分配三个buffer写入queue中
        AudioQueueAllocateBuffer(queue, 1024, &buffer[i]);
        AudioQueueEnqueueBuffer(queue, buffer[i], 0, NULL);
    }
}
//播放，使用AudioQueueStart(queue, NULL)
//运行阶段(1)queue启动后会播放第一个buffer（2）播放完第一个buffer之后会继续播放第二buffer，但是与此同时将第一个buffer回调给业务层由开发者进行填充，填充完毕重新入队；（3）第二个buffer播放完毕后，会继续播放第三个buffer，与此同时会将第二个buffer回调给业务层由开发者进行填充，填充完毕重新入队；（4）第三个buffer播放完毕后，会继续循环播放队列中的第一个buffer，也会将第三个buffer回调给业务层由开发者进行填充，填充完毕重新入队。
//9、iOS的音频格式是ABSD，结构体如下
/*
 struct AudioStreamBasicDescription {
     AudioFormatID       mFormatID;
     Float64             mSampleRate;
     UInt32              mChannelsPerFrame;
     UInt32              mFramesPerPacket;
     AudioFormatFlags    mFormatFlags;
     UInt32              mBytesPerPacket;
     UInt32              mBytesPerFrame;
     UInt32              mBitsPerChannel;
     UInt32              mReserved;
 }
 
 配置例子
 ```plain
 UInt32 bytesPerSample = sizeof(Float32);
 AudioStreamBasicDescription asbd;
 bzero(&asbd, sizeof(asbd));
 asbd.mFormatID = kAudioFormatLinearPCM;
 asbd.mSampleRate = _sampleRate;
 asbd.mChannelsPerFrame = channels;
 asbd.mFramesPerPacket = 1;
 asbd.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
 asbd.mBitsPerChannel = 8 * bytesPerSample;
 asbd.mBytesPerFrame = bytesPerSample;
 asbd.mBytesPerPacket = bytesPerSample;
 - mFormatID这个参数是用来指定音频的编码格式，此处音频编码格式指定为PCM格式；
 - 接下来设置声音的采样率、声道数以及每个Packet有几个Frame这三个参数；
 - mFormatFlags是用来描述声音表示格式的参数，代码中的第一个参数指定每个sample的表示格式是Float格式。这个类似于我们之前讲解的每个sample使用两个字节（SInt16）来表示；然后是后面的参数NonInterleaved，表面理解这个单词的意思是非交错的，其实对音频来说，就是左右声道是非交错存放的，实际的音频数据会存储在一个AudioBufferList结构中的变量mBuffers中。 **如果mFormatFlags指定的是NonInterleaved，那么左声道就会在mBuffers\[0\]里面，右声道就会在mBuffers\[1\]里面，而如果mFormatFlags指定的是Interleaved的话，那么左右声道就会交错排列在mBuffers\[0\]里面，** 理解这一点对后续的开发是十分重要的；
 - 接下来的mBitsPerChannel表示的是一个声道的音频数据用多少位来表示，前面我们已经知道每个采样使用Float来表示，所以这里就使用8乘以每个采样的字节数来赋值；
 - 最后是参数mBytesPerFrame和mBytesPerPacket的赋值，这里需要根据mFormatFlags的值来分配。如果在NonInterleaved的情况下，就赋值为bytesPerSample（因为左右声道是分开存放的）；但如果是Interleaved的话，那么就应该是bytesPerSample \* channels（因为左右声道是交错存放的），这样才能表示一个Frame里面到底有多少个byte。
 */

/*
 打开一个音频文件
 */
- (void)openFile {
    NSString *filePath;
    AudioFileID _mAudioFile;
    NSURL *fileURL = [NSURL URLWithString:filePath];
    OSStatus status = AudioFileOpenURL((__bridge CFURLRef)fileURL, kAudioFileReadPermission, kAudioFileCAFType, &_mAudioFile);
    UInt32 dataFormatSize = 1;//sizeof(dataFormat);
    //udioFileGetProperty(_mAudioFile, kAudioFilePropertyDataFormat, &dataFormatSize, &dataformat);
    
}//
//第二课

@end
